package src.serverSide;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

/**
 * 
 * @author John Stewart
 *
 * This class holds all the password, username pairs
 * It handles operations relating to the password
 * It also handles access attempts and times
 */
public class PassLog {

	/** contains the password hashs mapped to their integer addresses */
	HashMap<Integer,Password> passwords = new HashMap<Integer,Password>();
	
	/**
	 * Registers the given password to the given userid
	 *
	 * This method creates a new Password class and stores the password given
	 * as its hash, a byte array.  This instance is then stored in the HashMap passwords
	 * mapped to the userid given.  If the userid is already in passwords, the system flags an error.
	 *
	 * @param userid   The id integer associated with the password
	 * @param password The password to be stored
	 */
	public void addPass(int userid, String password) {
		if (!passwords.containsKey(new Integer(userid))) {
			byte[] hash = getHash(password);
			passwords.put(userid, new Password(hash));
		} else System.out.println("ERROR: Duplicate userid sent to PassLog.addPass()");
	}
	
	/**
	 * Checks the given password to the password under the given userid
	 *
	 * @param userid   The id integer associated with the password to check
	 * @param password The password to be checked
	 * @return         Returns true if the password matches, false otherwise
	 */
	public LoginResult checkPass(int userid, String password) {
		if (passwords.containsKey(new Integer(userid))) {
			Password pass = passwords.get(new Integer(userid));
			if (pass.checkAttempts()) {
				if (pass.check(getHash(password))) {
					pass.resetAccess();
					return LoginResult.success;
				} else {
					pass.setAccess();
					return LoginResult.invalidpassword;
				}
			} else return LoginResult.lockedlogin;
		} else return LoginResult.invaliduserid;
	}
	
	/**
	 * Checks the given password to the password under the given userid, then stores a new password
	 *
	 * @param userid   The id integer associated with the password to check
	 * @param password The password to be checked
	 * @return         Returns true if the password matches, false otherwise
	 */
	public LoginResult setPass(int userid, String password, String newpassword) {
		if (passwords.containsKey(new Integer(userid))) {
			Password pass = passwords.get(new Integer(userid));
			if (pass.checkAttempts()) {
				if (pass.check(getHash(password))) {
					pass.resetAccess();
					pass.setPassword(getHash(newpassword));
					return LoginResult.success;
				} else {
					pass.setAccess();
					return LoginResult.invalidpassword;
				}
			} else return LoginResult.lockedlogin;
		} else return LoginResult.invaliduserid;
	}
	
	/**
	 * Sets the access attempts to 0 for the listed user
	 * @param userid The user to unlock
	 */
	public void unlock(int userid) {
		if (passwords.containsKey(new Integer(userid))) {
			Password pass = passwords.get(new Integer(userid));
			pass.resetAccess();
		}
	}
	
	/**
	 * Removes the item associated with userid
	 * @param userid The ID of the object to remove
	 */
	public void removeUser(int userid) {
		passwords.remove(new Integer(userid));
	}
	
	/**
	 * Checks the structure associated with userid for recent activity
	 *
	 * @param userid   The id integer associated with the account
	 * @return         Returns true if there is recent activity
	 */
	public boolean checkActivity(int userid) {
		if (passwords.containsKey(new Integer(userid))) {
			Password pass = passwords.get(new Integer(userid));
			return pass.checkAccess();
		}
		return false;
	}
	
	/**
	 * Sets the activity of the user to current
	 * @param userid   The id integer associated with the activity to update
	 */
	public void setActivity(int userid) {
		if (passwords.containsKey(new Integer(userid))) {
			Password pass = passwords.get(new Integer(userid));
			pass.resetAccess();
		}
	}
	
	/**
	 * Checks if the password follows the conventions inside this method
	 * @param password The password to test
	 * @return True if it passes
	 */
	public boolean checkPasswordFormat(String password) {
		System.out.println("checkPasswordFormat in PassLog not implemented yet");
		return true;
	}
	
	/**
	 * When given a String, returns the hash representation
	 *
	 * This method hashes a String.  It provides a unique, irreversible representation of the String
	 * <p>
	 * Taken in part from https://www.owasp.org/index.php/Hashing_Java
	 *
	 * @param password The string to hash
	 * @return         Returns the byte array generated from the password
	 */
	private byte[] getHash(String password) {
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance("SHA-1");
			digest.reset();
			return digest.digest(password.getBytes("UTF-8"));
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
	private class Password {
		private byte[] hash;
		private long accesstime = System.currentTimeMillis();
		private byte attempts = 0;
		
		public Password(byte[] newhash) {
			setPassword(newhash);
		}
		
		public boolean check(byte[] newhash) {
			if (hash.length != newhash.length) return false;
			for (int i = 0; i < hash.length; i++) if (hash[i] != newhash[i]) return false;
			return true;
		}
		
		public void setAccess() {
			accesstime = System.currentTimeMillis();
			attempts++;
		}
		
		public void resetAccess() {
			accesstime = System.currentTimeMillis();
			attempts = 0;
		}
		
		public boolean checkAccess() {
			return (System.currentTimeMillis() - accesstime) < 1800000;
		}
		
		public boolean checkAttempts() {
			if (attempts >= 3 && checkAccess()) return false;
			else return true;
		}
		
		public void setPassword(byte[] newhash) {
			hash = new byte[newhash.length];
			System.arraycopy(newhash, 0, hash, 0, newhash.length);
		}
		
	}
	
	/**
	 * This enumerated type indicates the result of the login attempt
	 * @author John Stewart
	 */
	public enum LoginResult {
		invalidpassword, invaliduserid, lockedlogin, success
	}
	
}
